/**
 * 
 * ETH AI GAME LIBRARY
 * 
 * Author: Michael Jaworski
 * Copyright (c) Michael Jaworski 2010
 * http://code.google.com/p/eth/
 * 
 * Licence Agreement
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *	
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

package lib.eth.systems.fuzzy
{
	import lib.eth.core.errors.EthError;
	import lib.eth.core.interfaces.IEthProcess;
	import lib.eth.core.interfaces.IRecyclable;
	import lib.eth.systems.fuzzy.functions.FuzzyFunctions;
	import lib.eth.systems.fuzzy.functions.IFuzzyFunction;
	import lib.eth.systems.fuzzy.operations.FuzzyOperations;
	import lib.eth.systems.fuzzy.operations.IFuzzyOperation;
	
	/**
	 * applies a single computation on two fuzzy curves to calculate a result curve
	 * 
	 * @see lib.eth.systems.fuzzy.operations.FuzzyOperations
	 * @see lib.eth.systems.fuzzy.operations.FuzzyFunctions
	 * @see lib.eth.systems.fuzzy.FuzzyCurve
	 * 
	 * the default operation is ADD
	 * the default modifier is ONE
	 * 
	 * result = operandOne OPERATION (operandTwo MULTIPLY modifier)
	 * 
	 * @note the result curve of the fuzzy operator can be used as an operand, but
	 * then the other operand must be from another fuzzy operator or fuzzy
	 * curve that is properly composed.
	 * 
	 * @note A fuzzy operator is not properly composed until both operands are set. 
	 * 
	 * 
	 * @version 0.1
	 * 
	 */ 
	public class FuzzyOperator implements IEthProcess
	{		
		/**
		 * the first operand used in the operator
		 */ 
		private var _operandOne:FuzzyCurve;
		
		/**
		 * the second operand used in the operator, the modifier is applied
		 * to this operand, but the operand is not altered
		 */ 
		private var _operandTwo:FuzzyCurve;
		
		/**
		 * the modifier to apply to the second operand
		 * 
		 * the default is ONE, so no modification occurs (A = A * 1)
		 */ 
		public var modifier:IFuzzyFunction;
		
		/**
		 * the operation between the two operands
		 * 
		 * the default is ADD
		 */ 
		public var operation:IFuzzyOperation;
		
		/**
		 * the result of the operator after an update
		 */ 
		private var _result:FuzzyCurve;		
		
		/**
		 * defaults are set. the result curve is created and if 
		 * both operands are supplied, then the curve is initialized.
		 * 
		 * <p>
		 * pre  : class data is null<br />
		 * post : curve !== null, operation !== null, modifier !== null
		 * </p>
		 * 
		 */ 
		public function FuzzyOperator 
			(
				operandOneIn:FuzzyCurve = null,
				operandTwoIn:FuzzyCurve = null
			)
		{	
			// set the two operands
			_operandOne = operandOneIn;
			_operandTwo = operandTwoIn;
			
			// create the result curve
			_result = new FuzzyCurve ();
			
			// the default operation is ADD
			operation = FuzzyOperations.ADD;
			
			// default is ONE, which really means no modification (A * 1 = A)
			modifier = FuzzyFunctions.ONE;
			
			// make the result curve if both operands are supplied
			if (_operandOne !== null && _operandTwo !== null)
				initialize ();
		}
						
		/**
		 * @return the result curve
		 */ 
		public function get curve () : FuzzyCurve
		{ return _result; }			

		/**
		 * @return the first operand
		 */ 
		public function get operandOne () : FuzzyCurve
		{ return _operandOne; }			

		/**
		 * @return the second operand
		 */ 
		public function get operandTwo () : FuzzyCurve
		{ return _operandTwo; }			
		
		/**
		 * sets the first operand and resizes the curve if necessary
		 * 
		 * @param operandIn the first fuzzy curve (modifier is applied to operandTwo) 
		 */ 
		public function set operandOne (operandIn:FuzzyCurve) : void
		{ 
			_operandOne = operandIn;
			
			// if both are set, then re-initialize the result curve
			if (_operandTwo !== null)
				initialize ();
		}			
		
		/**
		 * sets the second operand and resizes the curve if necessary
		 * 
		 * @param operandIn the second fuzzy curve (modifier is applied to this operand) 
		 */ 
		public function set operandTwo (operandIn:FuzzyCurve) : void 
		{ 
			_operandTwo = operandIn;
			
			// if both are set, then re-initialize the result curve
			if (_operandOne !== null)
				initialize ();
		}			
		
		/**
		 * calculate all sample points for the result curve
		 *  
		 * <p>
		 * pre  : operandOne !== null, operandTwo !== null<br />
		 * post : result = (operandOne OPERATION (operandTwo x MODIFIER))
		 * </p>
     *
		 */
		public function update () : void
		{
			// grab the number of samples (points) and buckets (spaces between points)
			var samples:int = _result.samples;
			var bucketSize:Number = _result.sampleSize;
			
			// for the sample size - calculate all new point values 
			for (var index:int = 0; index < samples; index++)
			{
				// calculate x position (based on index of result)
				var x:Number = index * bucketSize; 
				
				// calculate operand 1 and 2 values
				var opOne:Number = _operandOne.getValue (x);
				var opTwo:Number = _operandTwo.getValue (x);
				
				// modify the second operand
				opTwo = opTwo * modifier.sample (x); 
				
				// perform calculation
				var result:Number = operation.compute (opOne, opTwo);
				
				// store the result
				_result.setIndex (index, result);
			}	
		}
		
		/**
		 * resize the result curve based on the size of the operands.
		 * 
		 * the result curve should be the size of the large of the two operands
		 */
		private function initialize () : void
		{
			// the size of the result curve
			var resolution:int = _operandOne.samples > _operandTwo.samples ? _operandOne.samples : _operandTwo.samples;
			
			if (resolution == 0)
				throw new EthError ('cannot calculate fuzzy operation on zero samples');
			
			// make new curve
			_result.controlled (this, resolution);
		}
	}
}